package com.jd.jobkeeper.server.service.db.impl;

import com.jd.jobkeeper.protocals.Task;
import com.jd.jobkeeper.server.data.entity.JobInfo;
import com.jd.jobkeeper.server.data.entity.TaskInfo;
import com.jd.jobkeeper.server.data.entity.TaskStatus;
import com.jd.jobkeeper.server.data.pesistence.TaskInfoRepository;
import com.jd.jobkeeper.server.scheduler.SchedulerContext;
import com.jd.jobkeeper.server.service.TaskService;
import com.jd.jobkeeper.utils.CronUtils;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@Service
public class DBTaskServiceImpl implements TaskService {

    final TaskInfoRepository repository;
    /**
     * namespace -> taskId -> taskInfo
     */
    final ConcurrentMap<String, ConcurrentMap<Long, TaskInfo>> nsTasksMap = new ConcurrentHashMap<>();
    final SchedulerContext context;

    public DBTaskServiceImpl(TaskInfoRepository repository, SchedulerContext context) {
        this.repository = repository;
        this.context = context;
    }


    @Override
    public Task findByJobId(Long jobId) {
        return null;
    }

    @Override
    public TaskInfo getLastTask(JobInfo jobInfo) {
        String namespace = jobInfo.getNamespaceCode();

        ConcurrentMap<Long, TaskInfo> taskMap = nsTasksMap.get(namespace);
        if (taskMap == null) {
            nsTasksMap.putIfAbsent(namespace, new ConcurrentHashMap<>());
            taskMap = nsTasksMap.get(namespace);
        }

        Long jobId = jobInfo.getId();
        TaskInfo taskInfo = taskMap.get(jobInfo.getId());
        if (taskInfo == null) {
            taskInfo = this.repository.findTopByJobIdOrderByTriggerTimeDesc(jobId);
            if (taskInfo == null) {
                return null;
            }
            taskMap.put(jobId, taskInfo);
        }
        return taskInfo;
    }

    @Override
    public TaskInfo produceTask(TaskInfo lastTask, JobInfo jobInfo) {

        TaskInfo taskInfo = new TaskInfo();
        taskInfo.setJobId(jobInfo.getId());
        taskInfo.setName(jobInfo.getName());
        Date now = new Date(); //current time
        taskInfo.setCreateTime(now);
        taskInfo.setModifyTime(now);
        taskInfo.setStatus(TaskStatus.New.getV());
        taskInfo.setParameter(jobInfo.getParameter());

        Date triggerTime = now;
        if (lastTask != null && ((now.getTime() - lastTask.getTriggerTime().getTime()) < jobInfo.getTimeInterVal())) {
            triggerTime = lastTask.getTriggerTime();
        }


        Date nextTriggerTime = CronUtils.calculateNextTriggerTime(triggerTime, jobInfo.getTimeExpression());
        taskInfo.setTriggerTime(nextTriggerTime);
        //taskInfo.setId(this.context.idGenerator().nextId());
        this.repository.saveAndFlush(taskInfo);
        this.nsTasksMap.get(jobInfo.getNamespaceCode()).put(jobInfo.getId(), taskInfo);
        return taskInfo;
    }


    @Override
    public void updateTask(Task task) {
        TaskInfo taskInfo = getLastTaskInfo(task);
        taskInfo.setStatus(TaskStatus.Completed.getV());
        this.repository.saveAndFlush(taskInfo);
    }

    @Override
    public boolean testAndSet(Task task) {
        TaskInfo taskInfo = getLastTaskInfo(task);
        if(taskInfo == null){
            return  false;
        }
        taskInfo.setStatus(TaskStatus.Running.getV());
        this.repository.saveAndFlush(taskInfo);
        return true;
    }

    private TaskInfo getLastTaskInfo(Task task){
        ConcurrentMap<Long, TaskInfo> taskMap = nsTasksMap.get(task.getNamespace());
        return taskMap == null ? null: taskMap.get(task.getJobId());
    }

    @Override
    public List<TaskInfo> findByJobIdAndStatus(Long jobId, Integer... statuses) {
        return this.repository.findByJobIdAndStatusContains(jobId, statuses);
    }

    @Override
    public Page<TaskInfo> page(String namespace, int page, int size) {
        TaskInfo taskInfo = new TaskInfo();
        taskInfo.setJobId(1L);

        ExampleMatcher exampleMatcher = ExampleMatcher.matching();
        Example example = Example.of(taskInfo, exampleMatcher);
        return this.repository.findAll(example, PageRequest.of(page, size, Sort.Direction.DESC, "createTime"));
    }
}
